﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Windows.Controls;
using System.Collections.Specialized;
using System.Windows;

namespace VSIXUtilities.GUI
{
	public class TreeViewAutoChecker : Component
	{
		public delegate void BeforeCheckEventHandler(object sender, CheckingEventArgs e);
		public delegate void AfterCheckEventHandler(object sender, CheckedEventArgs e);
		[Category("Behavior")]
		public event RoutedEventHandler ItemChecked;
		[Category("Behavior")]
		public event RoutedEventHandler ItemIndeterminate;
		[Category("Behavior")]
		public event RoutedEventHandler ItemUnchecked;
		[Category("Behavior")]
		public event BeforeCheckEventHandler BeforeParentCheck;
		public event AfterCheckEventHandler AfterChildCheck;
		private TreeView TreeViewInstance;
	
		#region Properties
		public TreeView Tree
		{
			get
			{
				return TreeViewInstance;
			}
			set
			{
				if(value == null)
				{
					throw new ArgumentNullException("Tree", "Tree property can not be null");
				}
				if(TreeViewInstance != null)
				{
					((INotifyCollectionChanged)TreeViewInstance.Items).CollectionChanged -= new NotifyCollectionChangedEventHandler(TreeViewItems_CollectionChanged);
				}
				TreeViewInstance = value;
				((INotifyCollectionChanged)TreeViewInstance.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(TreeViewItems_CollectionChanged);
			}
		}
		#endregion
		#region Events' Handlers
		private void CheckBoxTreeViewItem_Checked(object sender, RoutedEventArgs e)
		{
            ToggleButtonTreeViewItem<CheckBox> item = (ToggleButtonTreeViewItem<CheckBox>)sender;
			FireEvent(item);
			CheckChildren(item, item);
            if (item.Parent != null && item.Parent is ToggleButtonTreeViewItem<CheckBox>)
			{
                CheckAlongParents(item, (ToggleButtonTreeViewItem<CheckBox>)item.Parent, item.IsChecked);
			}
		}
        private void item_ItemsChanged(ToggleButtonTreeViewItem<CheckBox> item, object sender, NotifyCollectionChangedEventArgs e)
		{
			if(e.Action != NotifyCollectionChangedAction.Move && e.Action != NotifyCollectionChangedAction.Replace)
			{
				CheckAlongParents(item, item);
			}
		}
		private void TreeViewItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if(e.OldItems != null)
			{
				foreach(TreeViewItem item in e.OldItems)
				{
                    if (item is ToggleButtonTreeViewItem<CheckBox>)
					{
                        UnWireEvents((ToggleButtonTreeViewItem<CheckBox>)item);
					}
				}
			}
			if(e.NewItems != null)
			{
				foreach(TreeViewItem item in e.NewItems)
				{
                    if (item is ToggleButtonTreeViewItem<CheckBox>)
					{
                        WireEvents((ToggleButtonTreeViewItem<CheckBox>)item);
					}
				}
			}
		}
		#endregion
		#region Helper Functions
        private void CheckAlongParents(ToggleButtonTreeViewItem<CheckBox> EventSource, ToggleButtonTreeViewItem<CheckBox> item)
		{
			if(item.Items.Count == 0)
			{
				CheckAlongParents(EventSource, item, false);
			}
			else
			{
                CheckAlongParents(EventSource, item, ((ToggleButtonTreeViewItem<CheckBox>)item.Items[0]).IsChecked);
			}
		}
        private void CheckAlongParents(ToggleButtonTreeViewItem<CheckBox> EventSource, ToggleButtonTreeViewItem<CheckBox> Item, bool? StateOfAnyChildOfItem)
		{
			if(Item == null || Item.IsBeingUpdated)
			{
				return;
			}
			Item.BeginUpdate();
			bool? NewState = StateOfAnyChildOfItem;
			if(StateOfAnyChildOfItem.HasValue)
			{
                foreach (ToggleButtonTreeViewItem<CheckBox> itemBrother in Item.Items)
				{
					if(itemBrother.IsChecked != StateOfAnyChildOfItem)
					{
						NewState = null;
						break;
					}
				}
			}
			else
			{
				NewState = null;
			}
			if(BeforeParentCheck != null)
			{
				CheckingEventArgs e = new CheckingEventArgs(EventSource, Item, NewState);
				BeforeParentCheck(this, e);
				NewState = e.NewCheckStatus;
			}
			Item.IsChecked = NewState;
			Item.EndUpdate();
            if (Item.Parent != null && Item.Parent is ToggleButtonTreeViewItem<CheckBox>)
			{
                CheckAlongParents(EventSource, (ToggleButtonTreeViewItem<CheckBox>)Item.Parent, Item.IsChecked);
			}
		}
        private void CheckChildren(ToggleButtonTreeViewItem<CheckBox> EventSource, ToggleButtonTreeViewItem<CheckBox> Parent)
		{
			Parent.BeginUpdate();
			if(!Parent.IsChecked.HasValue)
			{
				Parent.IsChecked = false;
			}
            foreach (ToggleButtonTreeViewItem<CheckBox> child in Parent.Items)
			{
				if(Parent.IsChecked.HasValue != child.IsChecked.HasValue || Parent.IsChecked != child.IsChecked)
				{
					child.BeginUpdate();
					child.IsChecked = Parent.IsChecked;
					if(AfterChildCheck != null)
					{
						AfterChildCheck(this, new CheckedEventArgs(EventSource, child, child.IsChecked));
					}
					CheckChildren(EventSource, child);
					child.EndUpdate();
				}
			}
			Parent.EndUpdate();
		}
        private void UnWireEvents(ToggleButtonTreeViewItem<CheckBox> Item)
		{
			Item.Checked -= new RoutedEventHandler(CheckBoxTreeViewItem_Checked);
			Item.Indeterminate -= new RoutedEventHandler(CheckBoxTreeViewItem_Checked);
			Item.Unchecked -= new RoutedEventHandler(CheckBoxTreeViewItem_Checked);
			Item.ItemsChanged -= new NotifyItemsChangedEventHandler<CheckBox>(item_ItemsChanged);
            ((INotifyCollectionChanged)Item.Items).CollectionChanged -= new NotifyCollectionChangedEventHandler(TreeViewItems_CollectionChanged);
            foreach (TreeViewItem Childitem in Item.Items)
			{
                if (Childitem is ToggleButtonTreeViewItem<CheckBox>)
				{
                    UnWireEvents((ToggleButtonTreeViewItem<CheckBox>)Childitem);
				}
			}
		}
        private void WireEvents(ToggleButtonTreeViewItem<CheckBox> Item)
		{
			Item.Checked += new RoutedEventHandler(CheckBoxTreeViewItem_Checked);
			Item.Indeterminate += new RoutedEventHandler(CheckBoxTreeViewItem_Checked);
			Item.Unchecked += new RoutedEventHandler(CheckBoxTreeViewItem_Checked);
			Item.ItemsChanged += new NotifyItemsChangedEventHandler<CheckBox>(item_ItemsChanged);
            ((INotifyCollectionChanged)Item.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(TreeViewItems_CollectionChanged);
            foreach (TreeViewItem Childitem in Item.Items)
			{
                if (Childitem is ToggleButtonTreeViewItem<CheckBox>)
				{
                    WireEvents((ToggleButtonTreeViewItem<CheckBox>)Childitem);
				}
			}
		}
        private void FireEvent(ToggleButtonTreeViewItem<CheckBox> Item)
		{
			if(!Item.IsChecked.HasValue)
			{
				if(ItemIndeterminate != null)
				{
					ItemIndeterminate(Item, new RoutedEventArgs());
				}
			}
			else if(Item.IsChecked == true)
			{
				if(ItemChecked != null)
				{
					ItemChecked(Item, new RoutedEventArgs());
				}
			}
			else
			{
				if(ItemUnchecked != null)
				{
					ItemUnchecked(Item, new RoutedEventArgs());
				}
			}
		}
		#endregion
	}
}
